第14~16章 DOM
第十四章,DOM
散碎
NodeList 与 HTMLCollection、NamedNodeMap
- 类数组
- 实时数组,随文档变化
- HTMLCollection 多一个 namedItem 方法(以 name 属性值访问节点)
- querySelectorAll 返回 NodeList 快照,并非实时
Node 类型
DOM 一共有 12 种类型,以此为基类,确定类型的方法如下:
Node.类型名 === node.nodeType
类型名如下:

Element 类型(nodeType = 1
- 随便打印一个 div 元素,他的原型链是这样的:
HTMLDivElement --> HTMLElement --> Element --> Node --> EventTarget --> Object
- tagName 返回 标签名的大写形式
节点关系
.childNodes
- NodeList 类数组(这个有:forEach、values、keys、entries
- 实时反应此节点的子节点情况,并非快照
- 代码换行和空格会成为 text 节点出现在内(.children 不会)
- 里面的节点保持有双链表关系(非循环)(指针为:.previousSibling 和 .nextSibling)
.firstChild 和 .lastChild
分别指向 .childNodes[0] 和 .childNodes[childNodes.length - 1]
.hashChildNodes
是否有子节点;返回布尔值
由于空白节点会成为节点出现在 childNodes 上,所以在定位的时候会很麻烦
之前的 childNodes、firstChild、lastChild、previousSibling、nextSibling 都有了替换项
分别是 children、firstElementChild、lastElementChild、previousElementSibling、nextElementSibling
节点操作
注:如果添加的节点已经存在文档中,那么会移动此节点
同一个节点不会在文档中同时出现在两个或更多个地方
parentNode.appendChild(newNode):newNode // 添加节点到所有子节点最后面
parentNode.insertBefore(newNode, childNode):childNode // 添加到指定节点最前面
parentNode.insertBefore(newNode, null):childNode // 添加到子节点最后面
parentNode.replaceChild(newChild, childNode):childNode // 替换子节点
parentNode.removeChild(childNode):childNode // 删除子节点
node.cloneNode(isDeep) // 拷贝节点(不会复制 js 添加的属性 和 事件处理程序
node.createElement('tagName') // 创建 Element 节点
//节点就可以使用(缩小范围),不一定非得 document.
node.getElementByClassName('className')
node.querySelector('css选择器')
//......
属性
下面两种属性大部分都是相通的,互相影响;特殊情况已列出
DOM 属性
const container = document.querySelector('.container');
container.xxx;
HTML 属性
<div class="container" dir="rtl" style="color: red;" onclick="test()"></div>
HTML 属性操作(一般都用于自定义属性,其他用到的地方很少;但其实自定义属性也一般用 DOM 属性的 dataset 了。
container.getAttribute('key'); // 获取
container.setAttribute('key', value); // 添加/替换
container.removeAttribute('key');
DOM 属性 与 HTML 属性差异
- class
container.className === container.getAttribute('class'); // true
container.getAttribute('className'); // null
- style
container.style; // CSSStyleDeclaration 对象
container.getAttribute('style'); // "color: red;"
- 事件处理程序
container.onclick; // test 函数引用,没有则是 null
container.getAttribute('onclick'); // "test()"
- 自定义属性
- 自定义属性不会成为 DOM 属性(但会体现在 attributes 属性上)
- HTML5 规定自定义属性以 data- 开头,不然不会添加到 DOM 属性的 dataset 对象中
<div class="container" data-self="msg"></div>;
container.getAttribute('data-selef') === container.dataset['self']; // true
attributes
HTML 属性会反映在 DOM 属性 attributes 上

Document 类型(nodeType = 9
全局 document 原型链
HTMLDocument --> Document --> Node
子节点类型
document 的子节点类型只能是
DocumentType(比如)
Element(html)
ProcessingInstruction(xml)
Comment(注释)
iframe 文档跨域
取决于响应头中的 Content-Security-Policy 字段

其他集合
实时集合
- .anchors 所有带有 name 属性的 a 元素
- forms 所有 forms 元素
- images 所有 images 元素
- links 所有带有 href 的 a 元素
文档写入
document.write/writeln/open/close
write 和 writeln 就是后者写入后会多个换行;调用如果在加载页面后调用,页面则会被输出内容重写
调用 open 和 close 可以更好的控制何时写入何时结束,open 调用后文档子节点会清空
Text 类型(nodeType = 3
一般来说,大部分业务场景都能用 innerText 代替文本节点的操作方法
echars 的大量数据就用的 appendData
- 对于相邻的文本节点,可以执行以下 normalize 方法,将相邻文本节点合成一个
- 操作
textNode.appendData(text); // 往后增加 text
textNode.deleteData(start, count); // 从 start 出开始删除 count 个字符
textNode.insertData(start, text); // 在 start 前添加 text
textNode.replaceData(start, count, text); // 用法类似 array.splice。替换 start 到 start + count 为 text(左闭右开
textNode.splitText(offset); // 从 offset 位置拆分文本,原文本节点保留 [0, offset),返回 [offset, length - 1]
textNode.substringData(start, count); // 从 start 开始提取 count 个字符 [)
doc.createTextNode();
注释节点,和 Text 节点拥有类似方法,因为他们继承同一基类 CharacterData
document.createComment(); // 创建注释
CDATSection 类型 (nodeType = 4
继承 Text 类型;只在 XML 中有效
DocumentType 类型(nodeType = 10
DocumentFragment 类型(nodeType = 11
- 继承自 Node 所以可以像 document 那样使用,但是他只作为一个存放节点的仓库
- 如果文档中有节点被添加到文档片段,那么这个节点会在文档树中被删除
<div class="pop">hello</div>;
//
const pop = document.querySelector('.pop');
const documentFragment = document.createDocumentFragment();
documentFragment.appendChild(pop);
// pop 会在页面被移除
- 添加进文档的时候,文档片段本身不会被添加,只是他的子节点会被添加,且文档片段的子节点会被清空
console.log(documentFragment.childNodes); // NodeList[div.pop]
parentNode.appendChild(documentFragment);
console.log(documentFragment.childNodes); // NodeList[]
Attr 类型(nodeType = 2
一般就是 attributes 属性
document.createAttribute(); // 创建属性节点
表现形式:

将属性作为节点来访问多数情况下并无必要。推荐使用 getAttribute()、 removeAttribute() 和 setAttribute() 方法操作属性,而不是直接操作属性节点。
DOM 编程
动态脚本
- 添加外部 js
const script = document.createElement('script');
script.src = 'demo.js';
document.body.appendChild(script);
- 添加 js 源代码
const script = document.createElement('script');
script.text = 'function test(){ alert(1) }; test()';
document.body.appendChild(script);
用 innerHTML 添加的 script 代码永远不会执行
动态样式
也是同上可以通过创建 link 或者 style 节点进行添加,往 style 中添加内容可以用 innerText 或者 文本节点的方式都行。
操作表格
同上,但是相当繁琐,所以 table 元素自带了很多操作函数和访问属性

MutationObserver
MutationEvent 已废弃
切勿以为每次改变后回调都会执行,他的形式是 回调+记录队列 的形式;也就是每次改变都会添加到记录队列 mutationRecords 里,回调只在本次执行上下文观察到改变后执行一次
<div class="container"></div>
<div class="footer"></div>
<script>
const container = document.querySelector('.container');
const footer = document.querySelector('.footer');
// Boolean 配置默认值都是 false,至少有一个 Boolean 配置为 true
const MutationObserverInit = {
subtree: false, // 是否观察子节点的各种变化
characterData: false, // 是否观察文本节点变化(注:是文本节点的文本,不是节点的文本
attributes: true, // 是否观察属性值变化,不包括自定义 DOM 属性;包括 DOM、HTML、自定义 HTML 属性
childList: false, // 是否观察子节点变化(自身的 childNodes
characterDataOldValue: false, // 是否需要旧值,体现在 mutatedRecord 实例的 oldValue 上
attributeOldValue: false, // 是否需要旧值,体现在 mutatedRecord 实例的 oldValue 上
// attributeFilter: [] // 字符串数组,指定需要观察的属性
};
// 异步回调,微任务;会在观察的对象发生范围内的变化时添加到微任务队列中
const observer = new MutationObserver((mutationRecords, _observer) => {
console.log(mutationRecords, _observer === observer); // ⑤:[MutationRecord, MutationRecord, MutationRecord] true
});
// ①每次改变,改变记录会被存在记录列表,当异步回调执行时传递记录列表过去
observer.observe(container, MutationObserverInit);
observer.observe(footer, MutationObserverInit);
container.className = 'newClass';
container.id = 'app';
container.dataset.msg = 'msg';
container.自定义DOM属性 = '自定义DOM属性值'; // 不会被观察到
// ②取消观察,属于一刀切,会断开一切观察关系;前面的改变也不会被观察到了;不会结束实例生命周期
observer.disconnect();
container.className = 'disconnect-container'; // 不会被观察到
footer.className = 'discooent-footer'; // 不会被观察到
// ③任然可以继续观察
observer.observe(container, MutationObserverInit);
container.className = 'newClass'; // 至于是在这段代码后执行后添加异步回调,还是同步代码执行完添加,这不重要!
container.id = 'app';
container.dataset.msg = 'msg';
// ④提前取出记录列表,取出后回调不执行,但并未断开连接
console.log(observer.takeRecords()); // [MutationRecord, MutationRecord, MutationRecord]
// ⑤这才是回调所打印到的记录
container.className = 'newClass';
container.id = 'app';
container.dataset.msg = 'msg';
</script>
- mutationRecords

设置元素文本内容的标准方式是 textContent 属性,innerText 的定义不严谨,浏览器间的实现也存在兼容性问题,因此不建议再使用了。
第十五章,DOM 扩展
元素查找
querySelectorAll 返回 NodeList 快照,并非实时
matches 返回 boolean 是否有这个元素
HTML5
- classList 不是数组
DOMTokenList 实例

- document.activeElement
始终指向目前的焦点,默认情况下页面加载前为 null ,页面加载完后指向 body
多用于无障碍功能
- document.readyState
loading:文档正在加载
complete:文档加载完成
- document.compatMode
CSS1Compat:标准模式
BackCompat:混杂模式
- outerHTML
返回包括自身的序列化 DOM
<div class="box">1</div>
<script>
const box = document.querySelector('.box');
box.outerHTML = '<h1>he</h1>'; // 会取代 <div class="box">1</div>
</script>
- scrollIntoView
相对于第一个可滚动容器
/**
option 为布尔值
true: 滚动到顶部
false: 滚动到底部
option 为配置对象
{
behavior: 'auto', // 滚动动画 smooth auto
block: 'start', // 滚动到容器中垂直位置 start center end nearest
inline: 'nearest' // 滚动到容器中水平位置 start center end nearest
}
*/
element.scrollIntoView(option);
当元素聚焦时,聚焦元素也会滚动到可视范围,默认无动画,居中
- contains 与 compareDocumentPosition
判断参数和调用者关系
parentNode.contains(child: Node): boolean // 是否为后代
parentNode.compareDocumentPosition(child: Node): number
// 相对于 child 而言
0x1 不在
0x2 在前面
0x4 在后面
0x8 包含
0x10 被包含
child 是 parentNode 的后代, compareDocumentPosition 会返回 20,0x14; 异于常规思想;因为 child 既被包含,也在 parentNode 后面
- innerText 和 outerText
非标准
在用于读取值时, innerText 会按照深度优先的顺序将子树中所有文本节点的值拼接起来。在用于写入值时,innerText 会移除元素的所有后代并插入一个包含该值的文本节点。
第十六章,DOM2 和 DOM3
XML:一种数据格式,多用于存储数据,配置(类似 JSON 的功能
HTML:一种数据格式,多用于定义页面
XHTML:HTML 的严格子集,比如必须闭合标签、必须小写....
本章关于 XML 和 XHTML 略
node.style.float; // ❌
node.style.cssfloat; // ✔
// 节点
node.isSameNode(node: Node): boolean // 是否相同节点(引用相同
node.isEqualNode(node: Node): boolean // 节点是否相等(深克隆
// iframe
iframe.contentDocument
iframe.contentWindow
......